package com.yudaob2b.business.service.impl;


import com.yudaob2b.business.service.RedisLockService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author Kidd.Tan
 * @Date 2018/2/8
 * @Time 11:35
 */
@Service("redisLockService")
public class RedisLockServiceImpl implements RedisLockService {

    /**
     * 锁的后缀
     */
    private static final String LOCK_SUFFIX = "_redis_lock";
    /**
     * 重试时间
     */
    private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
    /**
     * 线程获取锁的等待时间
     */
    private int timeoutMsecs = 10 * 1000;
    /**
     * 锁超时时间，防止线程在入锁以后，防止阻塞后面的线程无法获取锁
     */
    private int expireMsecs = 60 * 1000;
    /**
     * 是否锁定标志
     */
    private volatile boolean locked = false;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取锁
     * @return 获取锁成功返回ture，超时返回false
     * @throws InterruptedException
     */
    @Override
    public synchronized boolean lock(String lockKeyPrefix, int timeoutMsecs) throws InterruptedException {
        if(StringUtils.isEmpty(lockKeyPrefix)) {
            return false;
        }
        String lockKey = getKeyName(lockKeyPrefix);
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            String expiresStr = String.valueOf(expires); //锁到期时间
            if (setNX(lockKey, expiresStr)) {
                locked = true;
                return true;
            }
            //redis里key的时间
            String currentValue = get(lockKey);
            //判断锁是否已经过期，过期则重新设置并获取
            if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //设置锁并返回旧值
                String oldValue = getSet(lockKey, expiresStr);
                //比较锁的时间，如果不一致则可能是其他锁已经修改了值并获取
                if (oldValue != null && oldValue.equals(currentValue)) {
                    locked = true;
                    return true;
                }
            }
            timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
            //延时
            Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
        }
        return false;
    }

    /**
     * 获取锁
     * @return 获取锁成功返回ture，超时返回false
     * @throws InterruptedException
     */
    @Override
    public synchronized boolean lock(String lockKeyPrefix) throws InterruptedException {
        return lock(lockKeyPrefix, this.timeoutMsecs);
    }

    /**
     * 释放获取到的锁
     */
    @Override
    public synchronized void unlock(String lockKeyPrefix) {
        if (locked) {
            String lockKey = getKeyName(lockKeyPrefix);
            //System.out.println("unlock loclkey: "+lockKey);
            redisTemplate.delete(lockKey);
            locked = false;
        }
    }

    /**
     * 获取redis业务锁key
     * @param lockKeyPrefix
     * @return
     */
    private String getKeyName(String lockKeyPrefix) {
        return lockKeyPrefix += LOCK_SUFFIX;
    }

    /**
     * 封装和jedis方法
     * @param key
     * @param value
     * @return
     */
    private boolean setNX(final String key, final String value) {
        boolean result = redisTemplate.opsForValue().setIfAbsent(key,value);
        //System.out.println("result: "+key);
        return result;
    }

    /**
     * 封装和jedis方法
     * @param key
     * @return
     */
    private String get(final String key) {
        Object obj = redisTemplate.opsForValue().get(key);
        return obj != null ? obj.toString() : null;
    }

    /**
     * 封装和jedis方法
     * @param key
     * @param value
     * @return
     */
    private String getSet(final String key, final String value) {
        Object obj = redisTemplate.opsForValue().getAndSet(key,value);
        return obj != null ? (String) obj : null;
    }
}
